/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.veditor.model;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.html.HTML;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import offset.nodes.Constants;
import offset.nodes.client.editor.model.DummyContentHandler;
import offset.nodes.client.editor.model.messages.TemplateInformation;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

class StyleSheetReader extends DummyContentHandler implements org.xml.sax.ContentHandler, org.xml.sax.ext.LexicalHandler {

    Template template = null;
    Stack<VirtualElement> elements = new Stack<VirtualElement>();
    VirtualElement root = null;
    StringBuffer html = new StringBuffer();
    StringBuffer content = new StringBuffer();
    String comment = null;
    Stack ignore = new Stack();
    HashMap<String, Template> templates = new HashMap<String, Template>();
    HashMap<String, String> nameToMatch = new HashMap<String, String>();
    VirtualBlock virtualBlock;

    StyleSheetReader(VirtualBlock virtualBlock) {
        this.virtualBlock = virtualBlock;
    }

    protected boolean ignore() {
        return ignore.size() > 0;
    }

    protected String removeEnclosingSpace(String text) {
        if (text == null)
            return null;
        int start = 0;
        int end = text.length();
        for (start = 0; start < text.length(); start++) {
            if (!Character.isSpaceChar(text.charAt(start)))
                break;
        }

        if (start >= text.length()-1) // only spaces
            return "";

        for (end = text.length() - 1; end >= 0; end--) {
            if (!Character.isSpaceChar(text.charAt(end)))
                break;
        }
        return text.substring(start, end + 1);
    }

    protected void addStartTag(String qName, Attributes atts) {
        VirtualElement element = new VirtualElement(qName, virtualBlock);
        if (elements.size() > 0)
            ((VirtualElement) elements.peek()).addChild(element);
        else
            root = element;
        elements.push(element);
        content = new StringBuffer();
        html.append("<" + qName + " ");
        for (int i = 0; i < atts.getLength(); i++) {
            String name = atts.getQName(i);
            String value = atts.getValue(i);
            html.append(name + "=\"" + value + "\"");
            ((SimpleAttributeSet) ((VirtualElement) elements.peek()).getAttributes()).addAttribute(name, value);
        }
        html.append(">\n");
    }

    protected void addAttributes(VirtualElement v, Attributes atts) {
        for (int i = 0; i < atts.getLength(); i++) {
            String name = atts.getQName(i);
            String value = atts.getValue(i);
            v.getAttributes().addAttribute(name, value);
        }
    }

    protected void addEndTag(String qName) {
        html.append("</" + qName + ">\n");
        if (elements.peek().getElementCount() == 0)
            elements.peek().setContent(content.toString());
        elements.pop();
    }

    public HashMap readStyleSheet(InputStream in, HashMap<String, TemplateInformation> nodeTypeInformation) throws SAXException, ParserConfigurationException, IOException {
        XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
        reader.setContentHandler(this);
        reader.setProperty("http://xml.org/sax/properties/lexical-handler", this);
        reader.parse(new InputSource(in));
        Iterator i = templates.values().iterator();
        while (i.hasNext()) {
            Template currentTemplate = (Template) i.next();
            TemplateInformation templateInformation = nodeTypeInformation.get(currentTemplate.getElementPath());
            currentTemplate.setTypeInformation(templateInformation);
            boolean occursMultiple = false;
            if (templateInformation != null && templateInformation.isMultiple())
                occursMultiple = true;
            currentTemplate.setOccursMultiple(occursMultiple);
            VirtualElement element = currentTemplate.getRoot();
            for (int j = 0; j < element.getElementCount(); j++) {
                VirtualElement child = (VirtualElement) element.getElement(j);
                if (child.isDataElement()) {
                    Template t = (Template) templates.get(nameToMatch.get(child.getName()));
                    for (int k = 0; k < t.getRoot().getElementCount(); k++) {
                        if (((VirtualElement) t.getRoot().getElement(k)).isDataElement())
                            ((VirtualElement) child).setIsLeaf(false);
                    }
                }
            }
        }
        return this.templates;
    }

    public void characters(char[] ch, int start, int length) throws SAXException {
        if (template == null || ignore())
            return;
        html.append(ch, start, length);
        content.append(ch, start, length);
    }

    public void endElement(String uri, String localName, String qName) throws SAXException {
        String ignored = null;
        if (ignore())
            ignored = (String) ignore.pop();

        if (template == null)
            return;
        if (qName.equals("xsl:template")) {
            template.setHtml(html.toString());
            template.setDescriptionText(removeEnclosingSpace(comment));
            if (root.getElementCount() > 0 && !((VirtualElement) root.getElement(0)).isDataElement())
                template.setRoot((VirtualElement) root.getElement(0));
            else
                template.setRoot(root);
            elements = new Stack();
            html = new StringBuffer();
            templates.put(template.getElementPath(), template);
            template = null;
            comment = null;
            return;
        }
        else if (qName.startsWith("xsl:"))
            return;
        else if (ignored != null && qName.equals(ignored))
            return;
        
        addEndTag(qName);
    }

    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
        if (ignore())
            ignore.push(qName);

        if (template == null) {
            if (qName.equals("xsl:template")) {
                String templateName = atts.getValue("name");
                if (templateName == null)
                    templateName = "";
                String match = atts.getValue("match");
                if (match == null)
                    match = "";
                template = new Template(templateName, match, virtualBlock);
                root = new VirtualElement(atts.getValue("match"), true, virtualBlock);
                elements.push(root);
                nameToMatch.put(atts.getValue("name"), atts.getValue("match"));
            }
            return;
        }
        if (qName.startsWith("xsl:")) {
            if (!ignore.contains("xsl:otherwise") && qName.equals("xsl:call-template")) {
                String name = atts.getValue("name");
                if (name.equals("hyperlink-of")) {
                    VirtualElement v = null;
                    try {
                        v = (VirtualElement) VirtualElement.BINARY.clone();
                        v.setVirtualBlock(virtualBlock);
                        addAttributes(v, atts);
                    } catch (CloneNotSupportedException ex) {
                        throw new SAXException(ex);
                    }
                    elements.peek().addChild(v);
                    return;
                } else if (!name.equals("value-of") && !name.equals("invokeAction")) {
                    VirtualElement element = new VirtualElement(atts.getValue("name"), true, virtualBlock);
                    elements.peek().addChild(element);
                }
                else if (comment != null)
                    html.append(comment);
            }
            else if (qName.equals("xsl:if"))
                return; // just ignore this one
            else if (!ignore())
                ignore.push(qName);
            return;
        }
        else if (doIgnoreLeaf(qName, atts)) {
            ignore.push(qName);
            return;
        }
        else if (atts.getValue(DataModel.ATTRIBUTE_NAME_BINARY) != null) {
            VirtualElement v = null;
            try {
                v = (VirtualElement) VirtualElement.BINARY.clone();
                addAttributes(v, atts);
            } catch (CloneNotSupportedException ex) {
                throw new SAXException(ex);
            }
            elements.peek().addChild(v);
            return;
        }

        addStartTag(qName, atts);
    }

    /**
     * Ignore certain leaf elements. For now, this is only the img element,
     * if it displays property values.
     *
     * This has been done to allow the ordinary editing of these property values.
     * 
     * @param qName
     * @param atts
     * @return
     */
    protected boolean doIgnoreLeaf(String qName, Attributes atts) {
        if (qName.equals(HTML.Tag.IMG.toString())) {
            String src = atts.getValue(HTML.Attribute.SRC.toString());
            String icon = Constants.PAR_CONTENT_TYPE + "=" + Constants.CONTENT_TYPE_ICON;
            if (src.indexOf(icon) >= 0)
                return true;
        }

        return false;
    }

    public void comment(char[] ch, int start, int length) throws SAXException {
        comment = new String(ch, start, length);
    }
}
